﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace Player
{
    public class PlayingProgressUpdatedEventArgs : EventArgs
    {
        public PlayingProgressUpdatedEventArgs(FrameBase frame, bool fastForward)
        {
            Frame = frame;
            FastForward = fastForward;
        }

        public FrameBase Frame { get; protected set; }
        public bool FastForward { get; protected set; }
    }

    public class PlayingThread : WorkerThread<PlayingProgressUpdatedEventArgs>
    {
        private readonly FrameBuffer m_frames;
        private FrameBase m_frame = null;

        public PlayingThread(FrameBuffer frames)
        {
            m_frames = frames;
        }

        private bool m_realTime = true;
        private object m_realTimeLocker = new object();
        public bool RealTimeMode
        {
            get { lock (m_realTimeLocker) return m_realTime; }
            set { lock (m_realTimeLocker) m_realTime = value; }
        }

        private double m_speed = 1;
        private object m_speedLocker = new object();
        public double Speed
        {
            get { lock (m_speedLocker) return m_speed; }
            set { lock (m_speedLocker) m_speed = value; }
        }

        protected override void Run(params object[] args)
        {
            bool firstFrame = true;
            long start = (long)args[0];
            bool continuePlaying = (bool)args[1];
            Log.Trace("Start: {0}", start);
            Log.Trace("Continue playing: {0}", continuePlaying);

            long lx = 0;
            DateTime lxs = DateTime.MinValue;

            #region Read a frame
            Action<object> frameRetrieve = (o) =>
            {
                m_frame = null;
                while (m_frame == null)
                {
                    m_frame = m_frames.Read();
                }
            };
            #endregion

            #region fast forward to the start time
            while (true)
            {
                if (!BlockAndWaitForStopOrResult(frameRetrieve, null, TimeSpan.FromMilliseconds(-1)))
                    return;

                if (m_frame.TimeStamp < start || firstFrame)
                {
                    Log.Trace("Fast forward frame: {0} [{1}]", m_frame.SystemTime, m_frame.TimeStamp);
                    lx = m_frame.TimeStamp;
                    lxs = m_frame.SystemTime;
                    Update(new PlayingProgressUpdatedEventArgs(m_frame, true));
                    firstFrame = false;
                }
                else if (m_frame.TimeStamp == start)
                {
                    Log.Trace("Fast forward last frame: {0} [{1}]", m_frame.SystemTime, m_frame.TimeStamp);
                    lx = m_frame.TimeStamp;
                    lxs = m_frame.SystemTime;
                    SyncUpdate(new PlayingProgressUpdatedEventArgs(m_frame, false));
                    break;
                }
                else
                {
                    DateTime systemTime = m_frame.SystemTime - new TimeSpan(m_frame.TimeStamp - start);
                    FrameBase fakeFrame = new FrameBase() { TimeStamp = start, SystemTime = systemTime };
                    Log.Trace("Fast forward last fakeframe: {0} [{1}]", fakeFrame.SystemTime, fakeFrame.TimeStamp);
                    SyncUpdate(new PlayingProgressUpdatedEventArgs(fakeFrame, false));
                    break;
                }

                Thread.Sleep(0);
            }
            #endregion

            if (ShouldStop) return;
            if (!continuePlaying) return;

            int updateInterval = ConsoleSettings.UpdateInterval;
            Log.Verbose("Update interval: {0}ms", updateInterval);
            long c = 0;
            long x1 = 0;
            Stopwatch m_stopWatch = Stopwatch.StartNew();
            firstFrame = true;
            double s = Math.Pow(5, Speed - 1);

            while (true)
            {
                if (ShouldStop) return;

                if (RealTimeMode)
                {
                    long now = m_stopWatch.Elapsed.Ticks;
                    if (firstFrame)
                    {
                        x1 = start;
                        c = now - x1;
                    }

                    long y = (long)(((double)m_frame.TimeStamp + (s - 1) * (double)x1) / s) + c;
                    TimeSpan time = TimeSpan.FromTicks(y - now);

                    while (true)
                    {
                        if (!RealTimeMode)
                        {
                            time = TimeSpan.Zero;
                            break;
                        }

                        y = (long)(((double)m_frame.TimeStamp + (s - 1) * (double)x1) / s) + c;
                        now = m_stopWatch.Elapsed.Ticks;
                        time = TimeSpan.FromTicks(y - now);
                        if (time <= TimeSpan.FromMilliseconds(updateInterval)) break;
                        Thread.Sleep(updateInterval);

                        now = m_stopWatch.Elapsed.Ticks;

                        FrameBase fakeFrame = new FrameBase();
                        long delta = (long)(s * (now - x1 - c));
                        fakeFrame.TimeStamp = x1 + delta;//now - c
                        fakeFrame.SystemTime = new DateTime(lxs.Ticks + (fakeFrame.TimeStamp - lx));

                        Log.Trace("Playing fake frame: {0} [{1}]", fakeFrame.SystemTime, fakeFrame.TimeStamp);
                        double s1 = Math.Pow(5, Speed - 1);
                        if (s != s1)
                        {
                            s = s1;
                            x1 = fakeFrame.TimeStamp;
                            c = now - fakeFrame.TimeStamp;
                        }
                        Update(new PlayingProgressUpdatedEventArgs(fakeFrame, false));
                        if (ShouldStop) return;
                    }

                    if (time > TimeSpan.Zero) Thread.Sleep(time);
                    else Thread.Sleep(0);

                    // Don't waste time to draw the frames on the buffer.  We just got another i-frame
                    if (m_frame.Type == 0)
                    {
                        //Frames.Empty();
                        // TODO: How do we remove the frames from dispatcher
                    }

                    lx = m_frame.TimeStamp;
                    lxs = m_frame.SystemTime;

                    Log.Trace("Frame type: {0}", m_frame.Type);
                    Log.Trace("Frame time: {0}", m_frame.SystemTime);
                    Log.Trace("Frame timestamp: {0}", m_frame.TimeStamp);

                    double s2 = Math.Pow(5, Speed - 1);
                    if (s != s2)
                    {
                        s = s2;
                        x1 = m_frame.TimeStamp;
                        c = now - m_frame.TimeStamp;
                    }

                    SyncUpdate(new PlayingProgressUpdatedEventArgs(m_frame, false));
                    if (ShouldStop) return;

                    if (!BlockAndWaitForStopOrResult(frameRetrieve, null, TimeSpan.FromMilliseconds(-1)))
                        return;
                    firstFrame = false;
                }
                else
                {
                    Log.Trace("Playing frame: {0} [{1}]", m_frame.SystemTime, m_frame.TimeStamp);
                    SyncUpdate(new PlayingProgressUpdatedEventArgs(m_frame, false));
                    if (ShouldStop) return;
                    Thread.Sleep((int)(1000 / Math.Pow(3, Speed)));

                    if (!BlockAndWaitForStopOrResult(frameRetrieve, null, TimeSpan.FromMilliseconds(-1)))
                        return;

                    // Preparing the the values for real time mode.  In case we switch back to real time mode in next round
                    lx = m_frame.TimeStamp;
                    lxs = m_frame.SystemTime;
                    s = Math.Pow(5, Speed - 1);
                    x1 = m_frame.TimeStamp;
                    c = m_stopWatch.Elapsed.Ticks - m_frame.TimeStamp;
                }
            }
        }
    }
}
